package com.sfac.springboot.modules.common.controller;

import com.sfac.springboot.modules.common.entity.ExceptionLog;
import com.sfac.springboot.modules.common.service.ExceptionLogService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.servlet.http.HttpServletRequest;

/**
 * 捕获所有控制器的异常并处理
 *
 * @author jzq
 * @date 2022/8/25 10:33
 */
@SuppressWarnings({"all"})
@ControllerAdvice
public class ExceptionController {

    public final static Logger LOGGER = LoggerFactory.getLogger(ExceptionController.class);

    @Autowired
    private ExceptionLogService exceptionLogService;


    /**
     * 根据异常类型和调用类型来决定返回的对象
     * 接口调用异常：返回403、404、500对应的JSON数据
     * 页面调用异常：返回403、404、500对应的错误页面
     *
     * @param e
     * @return ModelAndView 既能返回JSON，又能返回页面
     */
    @ExceptionHandler(value = Exception.class)
    public ModelAndView handleException(HttpServletRequest request, Exception e) {
        //输出异常信息
        e.printStackTrace();
        LOGGER.debug(e.getMessage());

        //插入异常信息
        insertExceptionLog(request, e);

        //根据异常类型，封装数据(状态码、消息、异常错误页面对应的路径)
        int status = 200;
        String message = "";
        String data = "";

        if (e instanceof UnauthorizedException) {
            status = 403;
            message = "没有权限！";
            data = "/common/403";
        } else {
            status = 500;
            message = "内部错误！";
            data = "/common/500";
        }

        //判断请求类型接口调用返回JSON数据，反之返回错误页面
        if (checkCallType(request)) {
            ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
            mv.addObject("status", status);
            mv.addObject("message", message);
            mv.addObject("data", data);
            return mv;
        } else {
            return new ModelAndView(new RedirectView(data));
        }

    }


    /**
     * 保存异常信息
     *
     * @param request
     * @param e
     */
    private void insertExceptionLog(HttpServletRequest request, Exception e) {
        ExceptionLog exceptionLog = new ExceptionLog();
        HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(
                "org.springframework.web.servlet.HandlerMapping.bestMatchingHandler");

        exceptionLog.setIp(request.getRemoteAddr());
        exceptionLog.setPath(request.getServletPath());
        exceptionLog.setClassName(handlerMethod.getBeanType().getName());
        exceptionLog.setMethodName(handlerMethod.getMethod().getName());
        exceptionLog.setExceptionType(e.getClass().getSimpleName());
        String message = e.getMessage();
        int length = 0;
        if (StringUtils.isNotBlank(message)) {
            length = message.length() > 200 ? 200 : message.length();
        }
        exceptionLog.setExceptionMessage(StringUtils.isNotBlank(message) ?
                e.getMessage().substring(0, length) : "");

        exceptionLogService.insertExceptionLog(exceptionLog);
    }


    /**
     * 判断请求控制器是接口还是页面
     *
     * @param request
     * @return
     */
    private boolean checkCallType(HttpServletRequest request) {
        HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(
                "org.springframework.web.servlet.HandlerMapping.bestMatchingHandler");
        // 从方法上面获取 ResponseBody 注解
        ResponseBody[] annotations1 = handlerMethod.getMethod().getAnnotationsByType(ResponseBody.class);
        // 从类上面获取 ResponseBody 注解
        ResponseBody[] annotations2 = handlerMethod.getBeanType().getAnnotationsByType(ResponseBody.class);
        // 从类上面获取 RestController 注解
        RestController[] annotations3 = handlerMethod.getBeanType().getAnnotationsByType(RestController.class);
        return (annotations1.length > 0) || (annotations2.length > 0) || (annotations3.length > 0);
    }

}
